﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Drawing;
using System.IO;

namespace TCPSocket_DLL {
    public enum DataStreamType {
        STRING,
        FILE,
        BITMAP
    };
    public class TcpSocket {
        #region EventHandlers
        //Private stuff
        private readonly EventArgs e;
        private EventHandler GetBuffSize;

        //Public stuff
        public EventHandler DataReceived;
        public EventHandler Connected;
        public EventHandler ConnectedToClient;
        public EventHandler Disconnected;
        public EventHandler Error;

        //Dummy method as default eventHandler Method
        void Dummy(object dummy, EventArgs e) { }
        #endregion

        #region Thread definitions
        //Listener Thread declaration
        private Thread thdListener;
        //Handler Thread declaration
        private Thread mThdHandler;
        #endregion

        #region Buffer variables, properties
        //Buffer Length
        private int mBuffLength;
        //Buffer Length Property
        public int BuffLength {
            get { return mBuffLength; }
            set { mBuffLength = value; }
        }
        //Buffer Array
        private byte[] mBuffer;
        #endregion

        #region The thing we need to build up a socket and stream between to users
        //TCP Listener and Client declaration
        private TcpListener mListener;
        private TcpClient mTcpClient;
        //Socket declarations
        private Socket mSenderSocket;
        private Socket mHandlerSocket;
        //NetworkStream declaration
        private NetworkStream mNetworkStream;
        //IP related declarations
        private IPEndPoint mIpEndPoint;
        private IPHostEntry mIpHost;
        private IPAddress[] mIpAddrs;

        #endregion

        #region State indicators
        //Indicator variables declaration
        private Boolean mIsServerStarted;
        private Boolean mIsHandlerStarted;
        private Boolean mIsReadyToSend;
        //Properties
        public Boolean IsServerStarted {
            get { return mIsServerStarted; }
        }
        public Boolean IsHandlerStarted {
            get { return mIsHandlerStarted; }
        }
        public Boolean IsReadyToSend {
            get { return mIsReadyToSend; }
        }
        #endregion

        #region Socket Host,Port and type variables
        //defines the type of the corrent socket
        private DataStreamType streamType;
        //Defines the port what it should listen
        private int mPort;
        //Public Port Setter
        public int SetPort {
            set { mPort = value; }
        }
        //Defines the host what it should listen
        private string mHost;
        //Public Host setter
        public string SetHost {
            set {
                mHost = value;
                mIsReadyToSend = true;
            }
        }
        #endregion

        #region Constructors
        private void SetDummyEventHandlers() {
            //load a dummy event handler this way i don't have to check if it's empty
            DataReceived += Dummy;
            Connected += Dummy;
            ConnectedToClient += Dummy;
            Disconnected += Dummy;
            Error += Dummy;
            GetBuffSize += Dummy;
        }
        private TcpSocket() {
            e = new EventArgs();
            //Create listener Thread
            thdListener = new Thread(new ThreadStart(ListenerThread));
            //create Handler Thread
            mThdHandler = new Thread(new ThreadStart(HandlerThread));
            //setting up the default values
            mIsServerStarted = false;
            mIsHandlerStarted = false;
            mIsReadyToSend = false;

            mBuffLength = 4;
        }
        public TcpSocket(int port)
            : this() {
            //setup default buffer size
            mBuffer = new byte[mBuffLength];
            //setup given port number
            SetPort = port;

            SetDummyEventHandlers();

        }
        public TcpSocket(int port, DataStreamType dt)
            : this() {

            SetPort = port;
            //set stream type
            streamType = dt;
            ////setup the different buffer length, that used for the stream types            
            //switch (DT) {
            //    case DATATYPE.FILE:
            //    case DATATYPE.BITMAP: buffLength = 1024;
            //        break;
            //    case DATATYPE.STRING: buffLength = 4;
            //        break;
            //}
            //buffer = new byte[buffLength];
            SetDummyEventHandlers();
        }
        public TcpSocket(string host, int port, DataStreamType dt)
            : this() {

            //setting u pthe given paramethers
            SetPort = port;
            SetHost = host;
            streamType = dt;
            SetDummyEventHandlers();
        }
        #endregion

        #region Server Start-Stop
        public void StartServer() {
            //set state
            mIsServerStarted = true;
            //StartListenerThread
            thdListener.Start();
        }
        public void StartHandler() {
            //set State
            mIsServerStarted = true;
            //Start Handler Thread
            mThdHandler.Start();
        }
        public void Stop() {
            //set state
            mIsServerStarted = false;
            /* Using this State variable to quit from threads, which are running in
             * infiniti loops. Using global variable to stop them.
             */
            try {
                if (mThdHandler.IsAlive)
                    mThdHandler.Join();
                if (thdListener.IsAlive)
                    thdListener.Join();
            } catch (ThreadStateException) { }
        }
        #endregion

        #region The things the Threads should do (Listener, Handler)
        public void ListenerThread() {
            //bind listener to the port in any IPs in the machine
            mListener = new TcpListener(IPAddress.Any, mPort);
            //Start listening
            mListener.Start();
            //Do until isStarted-Global variable
            do {
                //If Something is tring to connent do things else sleep and wait
                if (!mListener.Pending()) Thread.Sleep(2);
                else {
                    //Accepting the Connecting socket
                    mHandlerSocket = mListener.AcceptSocket();
                    //Who is that? who's connecting?
                    string endPointString = mHandlerSocket.RemoteEndPoint.ToString();
                    //Setup the stream to exchange data over it
                    mNetworkStream = new NetworkStream(mHandlerSocket);
                    //Make a Handler Thread
                    mThdHandler = new Thread(HandlerThread);
                    //and Starting it
                    mThdHandler.Start();
                    //And Call the connected Event Handler and send over the endpoint string
                    Connected.Invoke(endPointString + ";" + mPort.ToString(), e);
                }
            } while (IsServerStarted);
            //Stop listening if we stopping the thread
            mListener.Stop();
        }

        public void HandlerThread() {
            try {
                //lock resources
                lock (this) {
                    //What kind of stream is this?
                    switch (streamType) {
                        case DataStreamType.FILE:
                            throw new NotImplementedException();
                        case DataStreamType.BITMAP:
                            //Wait until the first data package is available on the network stream, and Handler is started
                            while (!mNetworkStream.DataAvailable && mIsServerStarted) Thread.Sleep(1);
                            //Loading image from stream, Loading is finished if streag get shut down
                            Image nBmap = Image.FromStream(mNetworkStream);
                            //Pass over Image Through an EventHandler
                            DataReceived.Invoke(nBmap, e);
                            break;
                        case DataStreamType.STRING:
                            do {
                                if (mNetworkStream.DataAvailable) {
                                    //building a dynamic array to store the available bytes into it
                                    List<byte> receivedBytes = new List<byte>();
                                    while (mNetworkStream.DataAvailable)
                                        //read into dynamic array
                                        receivedBytes.Add((byte)mNetworkStream.ReadByte());
                                    //Passing over received data Trough an EventHandler
                                    DataReceived.Invoke(receivedBytes.ToArray(), e);
                                } else Thread.Sleep(1);
                            } while (mIsServerStarted);
                            break;
                    }
                }
            } catch (Exception ex) {
                //If some error happend
                Error.Invoke(ex.Message, e);
            }
            try {
                //handlerSocket.Shutdown(SocketShutdown.Both);
                mHandlerSocket.Close();
            } catch (Exception)
            { }
        }
        #endregion

        #region Connectors
        private void Connect() {
            //Create a TCPClient Object
            mTcpClient = new TcpClient();
            //Connecting To the host
            mTcpClient.Connect(mHost, mPort);
            //setting up the stream based on the tcpClient connection
            mNetworkStream = mTcpClient.GetStream();
            //Call an Event to Say what happend
            Connected.Invoke(mHost + ":" + mPort, e);
        }
        private void Connect(string host) {
            //If no Port number were set up, load a default value
            if (mPort == 0) mPort = 8090;
            //setup host to the given destination
            SetHost = host;
            //call the base connector method
            Connect();
        }
        public void Connect(string host, int port) {
            //if the given port greate then 0 then setup
            if (port > 0) mPort = port;
            //setup host to the given destination
            SetHost = host;
            //call the base connector method
            Connect();
        }
        public void Connect(string host, int port, Boolean twoWayConn) {
            //call the connector method with the given dest. infos
            Connect(host, port);
            if (twoWayConn)
                StartHandler();
        }
        #endregion

        #region Socket Building, Closing
        private void BuildSocket() {
            //Init data connection, create new one
            mSenderSocket = new Socket(AddressFamily.InterNetwork,
                                           SocketType.Stream, ProtocolType.Tcp);

            if (mIpHost == null) {
                mIpHost = Dns.GetHostEntry(mHost);
                if (mIpAddrs == null) {
                    mIpAddrs = new IPAddress[mIpHost.AddressList.Length];
                    // use only IPv4
                    int z = 0;
                    for (int i = 0; i < mIpHost.AddressList.Length; i++) {
                        if (mIpHost.AddressList[i].AddressFamily == AddressFamily.InterNetwork) {
                            mIpAddrs[z++] = mIpHost.AddressList[i];
                        }
                    }
                }
                if (mIpEndPoint == null) mIpEndPoint = new IPEndPoint(mIpAddrs[0], mPort);
            }
            mSenderSocket.Connect(mIpEndPoint);
            mNetworkStream = new NetworkStream(mSenderSocket);
        }

        private void CloseSocket() {
            mSenderSocket.Close();
        }

        #endregion

        #region Sender Methods
        public void SetBufferSize(object nBuffSize, EventArgs e) {
            BuffLength = (int)Math.Pow(2, (int)nBuffSize);
            GetBuffSize = null;
        }
        public void SendBufferSize() {
            byte[] a = new byte[1];
            a[0] = (byte)Math.Log(BuffLength, 2);
            mNetworkStream.Write(a, 0, 1);
        }
        public void SendString(string data) {
            if (mNetworkStream != null) {
                mIsReadyToSend = false;
                mBuffer = new byte[data.Length];
                for (int i = 0; i < data.Length; i++)
                    mBuffer[i] = Convert.ToByte(data[i]);
                try {
                    mNetworkStream.Write(mBuffer, 0, mBuffer.Length);
                } catch (IOException) { return; }
                Thread.Sleep(100);
                mIsReadyToSend = true;
            }
        }
        public void SendBitmap(object bMap) {
            BuildSocket();
            if (mNetworkStream != null) {
                mIsReadyToSend = false;
                //lock (this)
                ((Image)bMap).Save(mNetworkStream, System.Drawing.Imaging.ImageFormat.Jpeg);
                mIsReadyToSend = true;
            }
            CloseSocket();
            //Close the connection
        }

        public void SendFile(string filePath) { throw new NotImplementedException(); }
        #endregion

    }
}
